package mathx

import (
	"math"
	"unsafe"
)

type Vec3 = Vec3T[float32]
type Vec3D = Vec3T[float64]

type Vec3T[T Float] struct {
	X, Y, Z T
}

func NewVector3[T Float](v ...T) Vec3T[T] {
	if len(v) == 3 {
		return Vec3T[T]{
			X: v[0], Y: v[1], Z: v[2],
		}
	} else {
		return Vec3T[T]{}
	}
}

func NewVector3D(v ...float64) Vec3D {
	if len(v) == 3 {
		return Vec3D{
			X: v[0], Y: v[1], Z: v[2],
		}
	} else {
		return Vec3D{}
	}
}

func (v Vec3T[T]) LengthSquare() T {
	return v.X*v.X + v.Y*v.Y + v.Z*v.Z
}

func (v Vec3T[T]) Length() float64 {
	return math.Sqrt(float64(v.X*v.X + v.Y*v.Y + v.Z*v.Z))
}

func (v Vec3T[T]) Add(pos *Vec3T[T]) Vec3T[T] {
	ret := Vec3T[T]{}
	ret.X = v.X + pos.X
	ret.Y = v.Y + pos.Y
	ret.Z = v.Z + pos.Z
	return ret
}

func (v Vec3T[T]) Sub(pos *Vec3T[T]) Vec3T[T] {
	ret := Vec3T[T]{}
	ret.X = v.X - pos.X
	ret.Y = v.Y - pos.Y
	ret.Z = v.Z - pos.Z
	return ret
}

func (v Vec3T[T]) Scale(rate T) Vec3T[T] {
	ret := Vec3T[T]{}
	ret.X = v.X * rate
	ret.Y = v.Y * rate
	ret.Z = v.Z * rate
	return ret
}

func (v Vec3T[T]) ToVector3() Vec3 {
	return Vec3{
		float32(v.X),
		float32(v.Y),
		float32(v.Z),
	}
}

func (v Vec3T[T]) ToVector3D() *Vec3D {
	return &Vec3D{
		float64(v.X),
		float64(v.Y),
		float64(v.Z),
	}
}

func (v Vec3T[T]) Dot(o *Vec3T[T]) T {
	return v.X*o.X + v.Y*o.Y + v.Z*o.Z
}

func (v Vec3T[T]) Cross(o *Vec3T[T]) Vec3T[T] {
	cx := v.Y*o.Z - v.Z*o.Y
	cy := v.Z*o.X - v.X*o.Z
	cz := v.X*o.Y - v.Y*o.X
	return Vec3T[T]{
		X: cx,
		Y: cy,
		Z: cz,
	}
}
func (v *Vec3T[T]) CopyFromFloat3(from *[3]float32) {
	v.X = T(from[0])
	v.Y = T(from[1])
	v.Z = T(from[2])
}

func (v *Vec3T[T]) CopyFromDouble3(from *[3]float64) {
	v.X = T(from[0])
	v.Y = T(from[1])
	v.Z = T(from[2])
}

func (v *Vec3T[T]) CopyFromVector3(from Vec3) {
	v.X = T(from.X)
	v.Y = T(from.Y)
	v.Z = T(from.Z)
}

func (v *Vec3T[T]) CopyFromVector3D(from Vec3D) {
	v.X = T(from.X)
	v.Y = T(from.Y)
	v.Z = T(from.Z)
}

func (v *Vec3T[T]) GetFloatPtr() *T {
	return &v.X
}

func (v *Vec3T[T]) GetFloat3Ptr() *[3]T {
	return (*[3]T)(unsafe.Pointer(&v.X))
}

func (v *Vec3T[T]) Normalize() {
	v.Divide(T(v.Length()))
}

func (v *Vec3T[T]) Divide(scalar T) {
	if scalar != 0 {
		invScalar := 1 / scalar
		v.X *= invScalar
		v.Y *= invScalar
		v.Z *= invScalar
	} else {
		v.X = 0
		v.Y = 0
		v.Z = 0
	}
}

func (v *Vec3T[T]) SetX(x T) {
	v.X = x
}

func (v *Vec3T[T]) SetY(y T) {
	v.Y = y
}

func (v *Vec3T[T]) SetZ(z T) {
	v.Z = z
}

func (v *Vec3T[T]) Set(x, y, z T) {
	v.X = x
	v.Y = y
	v.Z = z
}

func (v *Vec3T[T]) Unsafe() unsafe.Pointer {
	return unsafe.Pointer(v)
}

func (v *Vec3T[T]) UnsafePtr() uintptr {
	return uintptr(unsafe.Pointer(v))
}
